package top.amot.forceview

import android.content.Context
import timber.log.Timber
import top.amot.forceview.anim.Stepper
import top.amot.forceview.drawitem.AbstractDrawItem
import top.amot.forceview.drawitem.AbstractDrawLink
import top.amot.forceview.drawitem.DrawManager
import top.amot.forceview.drawitem.node.CircleNodeDrawItem
import top.amot.forceview.force.*
import top.amot.forceview.layout.I_Layout
import top.amot.forceview.model.*
import top.amot.forceview.theme.I_Theme
import kotlin.math.pow
import kotlin.math.sqrt

//https://juejin.cn/post/6874147411732725767

class Simulation private constructor(builder: Builder) {
    interface Callback {
        fun onTick()
        fun onEnd()
    }


    var alpha: Float
        private set
    val alphaMin: Float
    // alpha衰减率
    val alphaDecay: Float
    var alphaTarget: Float
    // 速度衰减
    private val velocityDecay: Float

    val forces = mutableMapOf<String, Force>()

    private var iterations = DEFAULT_ITERATIONS
    val nodeModel: AbstractNodeModel
    private val stepper by lazy {
        Stepper { this.step() }
    }
    private var callback: Callback? = null
    val layout: I_Layout
    val theme: I_Theme?
    var initialRadius: Float = INITIAL_RADIUS
    var initialAngle: Float = INITIAL_ANGLE

    var drawManager : DrawManager

    fun setIterations(i: Int): Simulation {
        iterations = i
        return this
    }

    /*val nodes: List<AbstractNode>
        get() = layout.currentNodeList()
    val links: List<AbstractLink<AbstractNode,AbstractNode>>
        get() = layout.currentLinkList()*/

    fun getVelocityDecay(): Float {
        return 1 - velocityDecay
    }


    fun setCallback(callback: Callback?) {
        this.callback = callback
    }

    fun restart() {
        stepper.restart()
    }

    fun stop() {
        stepper.stop()
    }

    fun destroy() {
        stepper.destroy()
        this.callback = null
    }

    private fun step() {
        tick(iterations)
        callback?.onTick()
        if (alpha < alphaMin) {
            stepper.stop()
            callback?.onEnd()
        }
    }

    private fun tick(iterationCount: Int) {
        var iterations = iterationCount
        if (iterations <= 0) {
            iterations = DEFAULT_ITERATIONS
        }
        for (k in 0 until iterations) {
            // alpha不断衰减
            alpha += (alphaTarget - alpha) * alphaDecay
            layout.applyForce(alpha,velocityDecay)
        }
    }

    init {
        initialRadius = builder.initialRadius;
        initialAngle = builder.initialAngle;
        alpha = builder.alpha
        alphaMin = builder.alphaMin
        alphaDecay = builder.alphaDecay
        alphaTarget = builder.alphaTarget
        velocityDecay = 1 - builder.velocityDecay
        nodeModel = builder.nodeModel
        layout = builder.layout
        theme = builder.theme

        forces.putAll(builder.forces)

        drawManager = builder.drawManager
    }

    class Builder {
        var initialRadius: Float = INITIAL_RADIUS
        var initialAngle: Float = INITIAL_ANGLE
        var alpha: Float = 1.0f
        var alphaMin: Float = 0.001f
        //var alphaMin: Float = 0.1f
        var alphaDecay: Float = 1 - alphaMin.pow(1.0f / 300)
        var alphaTarget: Float = 0.0f
        var velocityDecay: Float = 0.3f
        var forces = mutableMapOf<String, Force>()
        var nodeModel: AbstractNodeModel = NodeModel()
        lateinit var layout: I_Layout
        var theme: I_Theme? = null

        val drawManager : DrawManager = DrawManager()

        fun force(name: String, force: Force): Builder {
            forces[name] = force
            return this
        }

        fun initDefault(context: Context): Builder {
            putDrawNode(Node.TYPE,CircleNodeDrawItem(context))
            force(ForceLink.NAME,  ForceLink().distance { 350f }.strength { 2f })
            force(ForceCenter.NAME,  ForceCenter())
            force(ForceManyBody.NAME,  ForceManyBody().strength { node -> (node.level + 1f) * -3000f })
            force(ForceRadial.NAME,  ForceRadial().strength { 1f })
            force(ForceCollide.NAME,  ForceCollide().strength(10f).radius { node -> node.level * 10f })
            return this
        }

        fun <T:AbstractNode> putDrawNode(nodeType: String,nodeDrawItem: AbstractDrawItem<T>): Builder {
            drawManager.putDrawNode(nodeType,nodeDrawItem)
            return this
        }

        fun putDrawLink(linkType:String,linkDrawLink: AbstractDrawLink<AbstractLink<AbstractNode, AbstractNode>>): Builder {
            drawManager.putDrawLink(linkType,linkDrawLink)
            return this
        }

        fun nodeModel(nodeModel: AbstractNodeModel): Builder {
            this.nodeModel = nodeModel
            return this
        }

        fun initTheme(theme: I_Theme): Builder {
            this.theme = theme
            return this
        }

        fun initialLayout(layout: I_Layout): Builder {
            this.layout = layout
            return this
        }

        fun build(): Simulation {
            return Simulation(this)
        }
    }

    companion object {
        const val INITIAL_RADIUS = 30.0f
        val INITIAL_ANGLE = (Math.PI * (3 - sqrt(5.0))).toFloat()
        const val DEFAULT_ITERATIONS = 1
    }
}